/*
负载均衡器接口和实现
用于封装负载均衡相关逻辑

	防止单个服务器使用过载

2025/10/16
增加健康服务，选择节点只选择健康节点（为什么不只更新健康节点？）
*/
package locate

import (
	"fmt"
	"gameServer/registry"
	"math/rand" // 用于生成随机数（随机负载均衡算法依赖）
	"time"      // 用于初始化随机数种子（确保随机数不重复）
)

/* //* 负载均衡器实现，提供轮询、随机、基于路由的三种负载均衡算法，支持健康节点筛选，确保请求分发的高可用和均衡性 */

// 负载均衡器接口
type Balancer interface {
	// Select 选择节点：根据服务名和路由ID，从可用服务中选一个节点
	//
	// 参数：
	//   serviceName string：服务名（如"chat"、"game"，用于筛选同一类型的服务）
	//   route uint32：业务路由ID（如1001=发送聊天消息，不同算法对该参数的使用不同）
	// 返回值：
	//   *registry.ServiceInfo：选中的服务节点信息（含ID、地址、端口等）
	//   error：选择过程中的错误（如无可用服务）
	Select(serviceName string, route uint32) (*registry.ServiceInfo, error)
	// Update 更新服务列表：当后端节点上线/下线时，同步最新的服务列表到负载均衡器
	//
	// 参数：
	//   service []*registry.ServiceInfo：最新的服务列表（从注册中心获取）
	Update(service []*registry.ServiceInfo)

	// 获取健康服务
	GetHealthyServices(serviceName string) ([]*registry.ServiceInfo, error)
}

// 服务定位器接口
// type Locator interface {
// 	// 发布服务
// 	PublishServices(ctx context.Context, services []*registry.ServiceInfo) error
// }

// 轮训负载均衡器 按"顺序循环"的方式选择节点
// 适用于：后端节点性能一致，需均匀分配请求的场景（如无状态API服务）
type RoundRobinBalancer struct {
	// services 服务列表映射：key=服务名（如"chat"），value=该服务名下的所有节点信息切片
	// 作用：按服务名分组管理节点，避免不同类型服务（如chat和game）混选
	services map[string][]*registry.ServiceInfo
	// counters 轮询计数器映射：key=服务名，value=该服务名下一次要选择的节点索引
	// 作用：记录每个服务的轮询进度，确保每次选择下一个节点（循环）
	counters map[string]int
}

// ErrNoServiceAvailable 无可用服务错误 当负载均衡器中无目标服务的节点时返回
var (
	ErrNoServiceAvailable        = fmt.Errorf("no service available")
	ErrNoHealthyServiceAvailable = fmt.Errorf("no healthy service available")
)

// 创建轮询负载均衡器
func NewRoundRobinBalancer() *RoundRobinBalancer {
	return &RoundRobinBalancer{
		services: make(map[string][]*registry.ServiceInfo),
		counters: make(map[string]int),
	}
}

// 获取健康服务列表
func (b *RoundRobinBalancer) GetHealthyServices(serviceName string) ([]*registry.ServiceInfo, error) {
	services, exists := b.services[serviceName]
	if !exists {
		return nil, ErrNoServiceAvailable
	}
	var healthyServices []*registry.ServiceInfo
	for _, service := range services {
		if service.Healthy {
			healthyServices = append(healthyServices, service)
		}
	}
	return healthyServices, nil
}

// 选择节点（轮询算法）（只选择健康节点）
// 轮询选择节点：按服务名找到节点列表，按计数器顺序选择下一个节点
//
// 参数：
//
//	serviceName string：目标服务名（如"chat"，筛选要选的节点类型）
//	route uint32：业务路由ID（轮询算法不使用该参数，仅为满足Balancer接口签名）
//
// 返回值：
//
//	*registry.ServiceInfo：选中的节点信息；error：错误（如无可用服务）
func (b *RoundRobinBalancer) Select(serviceName string, route uint32) (*registry.ServiceInfo, error) {
	// 根据服务名从services map中获取该服务的所有节点列表
	services, exists := b.services[serviceName]
	if !exists || len(services) == 0 {
		return nil, ErrNoServiceAvailable
	}
	// 过滤健康节点
	healthyServices, _ := b.getHealthyServices(serviceName)
	if len(healthyServices) == 0 {
		return nil, ErrNoHealthyServiceAvailable
	}
	// 获取并递增计数器
	// 获取当前服务的轮询计数器（初始为0，map中无该key时默认值为0）
	counter := b.counters[serviceName]
	// 更新计数器：下一次选择的索引 = (当前索引 + 1) % 节点数量（实现循环，避免越界）
	// 例如：3个节点，计数器0→1→2→0循环
	b.counters[serviceName] = (counter + 1) % len(services)
	// 轮询器计算出索引位置，然后services就用那个位置的服务节点
	return healthyServices[counter], nil
}

// 获取健康服务
func (b *RoundRobinBalancer) getHealthyServices(serviceName string) ([]*registry.ServiceInfo, error) {
	services, exists := b.services[serviceName]
	if !exists {
		return nil, ErrNoServiceAvailable
	}
	var healthyServices []*registry.ServiceInfo
	for _, service := range services {
		if service.Healthy {
			healthyServices = append(healthyServices, service)
		}
	}
	return healthyServices, nil
}

// Update 更新轮询负载均衡器的服务列表：重置services map，按服务名重新分组存储新节点
//
// 参数：
//
//	services []*registry.ServiceInfo：从注册中心获取的最新服务列表（含所有在线节点）
func (b *RoundRobinBalancer) Update(services []*registry.ServiceInfo) {
	// 初始化新的services map（清空旧数据，避免旧节点残留）
	b.services = make(map[string][]*registry.ServiceInfo)
	// 遍历新服务列表，按服务名分组存入services map
	for _, service := range services {
		// service.Name：当前节点的服务名（如"chat"）
		// append(b.services[service.Name], service)：将节点追加到对应服务名的切片中
		// map中无该服务名时，默认值为nil切片，append会自动创建切片
		b.services[service.Name] = append(b.services[service.Name], service)
	}
	// 注：轮询计数器counters无需重置，因为新服务列表的节点数量可能变化，下次Select会自动通过取模适配
}

// RandomBalancer 随机负载均衡器：随机选择一个节点
// 适用于：后端节点性能有差异，无需固定路由-节点映射的场景（如非关键查询服务）
type RandomBalancer struct {
	// services 服务列表映射：同RoundRobinBalancer，按服务名分组存储节点
	services map[string][]*registry.ServiceInfo
}

// 创建随机负载均衡器实例
//
// 返回值：*RandomBalancer：初始化后的随机负载均衡器指针
func NewRandomBalancer() *RandomBalancer {
	// 初始化随机数种子：基于当前时间的纳秒数，确保每次运行生成的随机数不同（避免重复）
	rand.Seed(time.Now().UnixNano())
	return &RandomBalancer{
		services: make(map[string][]*registry.ServiceInfo),
	}
}

// 选择节点（随机算法）
// Select 随机选择节点：按服务名找到节点列表，生成随机索引选择节点
//
// 参数：
//
//	serviceName string：目标服务名；route uint32：路由ID（随机算法不使用，仅满足接口签名）
//
// 返回值：*registry.ServiceInfo：选中的节点；error：错误（如无可用服务）
func (b *RandomBalancer) Select(serviceName string, route uint32) (*registry.ServiceInfo, error) {
	services, exists := b.services[serviceName]
	if !exists || len(services) == 0 {
		return nil, ErrNoServiceAvailable
	}
	// 获取健康服务列表
	healthyServices := b.getHealthyServices(serviceName)
	if len(healthyServices) == 0 {
		return nil, ErrNoHealthyServiceAvailable
	}

	// 生成随机索引：rand.Intn(n)返回[0, n)的随机整数（n=节点数量，确保索引在有效范围内）
	index := rand.Intn(len(services))
	return healthyServices[index], nil
}

// 获取健康服务列表
func (b *RandomBalancer) GetHealthyServices(serviceName string) ([]*registry.ServiceInfo, error) {
	services, exists := b.services[serviceName]
	if !exists {
		return nil, ErrNoServiceAvailable
	}

	var healthyServices []*registry.ServiceInfo
	for _, service := range services {
		if service.Healthy {
			healthyServices = append(healthyServices, service)
		}
	}

	return healthyServices, nil
}

// getHealthyServices 内部方法：获取健康服务列表
func (b *RandomBalancer) getHealthyServices(serviceName string) []*registry.ServiceInfo {
	services, exists := b.services[serviceName]
	if !exists {
		return nil
	}

	var healthyServices []*registry.ServiceInfo
	for _, service := range services {
		if service.Healthy {
			healthyServices = append(healthyServices, service)
		}
	}

	return healthyServices
}

// 更新服务列表
func (b *RandomBalancer) Update(services []*registry.ServiceInfo) {
	b.services = make(map[string][]*registry.ServiceInfo)
	for _, service := range services {
		b.services[service.Name] = append(b.services[service.Name], service)
	}
}

// 基于路由的负载均衡器：按业务路由ID取模选择节点
// 适用于：需将同一路由的请求固定到同一节点的场景（如会话绑定、状态存储业务）
type RouteBasedBalancer struct {
	services map[string][]*registry.ServiceInfo
}

// 创建基于路由的负载均衡器
func NewRouteBaseBalancer() *RouteBasedBalancer {
	return &RouteBasedBalancer{
		services: make(map[string][]*registry.ServiceInfo),
	}
}

// 选择节点（基于路由算法）
// Select 基于路由选择节点：按服务名找节点列表，路由ID取模选择固定节点
//
// 参数：
//
//	serviceName string：目标服务名；route uint32：业务路由ID（核心参数，用于取模）
//
// 返回值：*registry.ServiceInfo：选中的节点；error：错误（如无可用服务）
func (b *RouteBasedBalancer) Select(serviceName string, route uint32) (*registry.ServiceInfo, error) {
	services, exists := b.services[serviceName]
	if !exists || len(services) == 0 {
		return nil, ErrNoServiceAvailable
	}
	// 获取健康服务列表
	healthyServices := b.getHealthyServices(serviceName)
	if len(healthyServices) == 0 {
		return nil, ErrNoHealthyServiceAvailable
	}
	// 路由ID取模计算索引：index = 路由ID（转int） % 节点数量
	// 作用：确保同一路由ID始终得到同一索引（固定节点），不同路由ID均匀分布
	index := int(route) % len(services)
	return healthyServices[index], nil
}

// 更新服务列表
func (b *RouteBasedBalancer) Update(services []*registry.ServiceInfo) {
	b.services = make(map[string][]*registry.ServiceInfo)
	for _, service := range services {
		b.services[service.Name] = append(b.services[service.Name], service)
	}
}

// 获取健康服务列表
func (b *RouteBasedBalancer) GetHealthyServices(serviceName string) ([]*registry.ServiceInfo, error) {
	services, exists := b.services[serviceName]
	if !exists {
		return nil, ErrNoServiceAvailable
	}

	var healthyServices []*registry.ServiceInfo
	for _, service := range services {
		if service.Healthy {
			healthyServices = append(healthyServices, service)
		}
	}

	return healthyServices, nil
}

// getHealthyServices 内部方法：获取健康服务列表
func (b *RouteBasedBalancer) getHealthyServices(serviceName string) []*registry.ServiceInfo {
	services, exists := b.services[serviceName]
	if !exists {
		return nil
	}

	var healthyServices []*registry.ServiceInfo
	for _, service := range services {
		if service.Healthy {
			healthyServices = append(healthyServices, service)
		}
	}

	return healthyServices
}
